﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Web;
using System.Web.Compilation;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

using AjaxControlFramework.Reflection;

namespace AjaxControlFramework
{
    public class AjaxControlHandler : IHttpHandler, IRequiresSessionState
    {
        //------// Properties \\--------------------------------------------\\
        public bool IsReusable
        {
            get { return true; }
        }


        public HttpResponse Response
        {
            get { return HttpContext.Current.Response; }
        }


        public static bool IsCallback
        {
            get { return (!String.IsNullOrEmpty(ControlID) && !String.IsNullOrEmpty(ControlParam)); }
        }


        public static string ControlID
        {
            get { return HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.Form["__AJAXCONTROLID"]); }
        }


        public static string ControlParam
        {
            get { return HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.Form["__AJAXPARAM"]); }
        }


        public static CallbackStrategyArguments CallbackArguments
        {
            get { return (CallbackStrategyArguments)HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.CallbackArguments"]; }
            protected set { HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.CallbackArguments"] = value; }
        }


        public static AjaxControlContext ControlContext
        {
            get { return (AjaxControlContext)HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.ControlContext"]; }
            protected set { HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.ControlContext"] = value; }
        }


        public static AjaxControlHandler Current
        {
            get { return (AjaxControlHandler)HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.Current"]; }
            protected set { HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.Current"] = value; }
        }


        public static Page CurrentPage
        {
            get { return (Page)HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.CurrentPage"]; }
            protected set { HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.CurrentPage"] = value; }
        }


        public static System.Web.UI.ScriptManager CurrentScriptManager
        {
            get
            {
                Page currentPage = CurrentPage;

                if (currentPage == null)
                {
                    return null;
                }
                else
                {
                    System.Web.UI.ScriptManager scriptManager = (System.Web.UI.ScriptManager)currentPage.Items[typeof(System.Web.UI.ScriptManager)];

                    if (scriptManager == null)
                    {
                        scriptManager = System.Web.UI.ScriptManager.GetCurrent(currentPage);
                    }

                    return scriptManager;
                }
            }
            protected set
            {
                Page currentPage = CurrentPage;

                if (currentPage != null)
                {
                    currentPage.Items[typeof(System.Web.UI.ScriptManager)] = value;
                }
            }
        }
        //------\\ Properties //--------------------------------------------//



        //------// Fields \\------------------------------------------------\\
        protected static Type TypeOfPage = typeof(System.Web.UI.Page);
        protected static Type TypeOfControl = typeof(System.Web.UI.Control);
        //------\\ Fields //------------------------------------------------//



        //------// Constructors \\------------------------------------------\\
        public AjaxControlHandler()
        {

        }
        //------\\ Constructors //------------------------------------------//



        //------// Methods \\-----------------------------------------------\\
        public void ProcessRequest(HttpContext context)
        {
            Current = this;


            /*
             * Page Life-Cycle Events To Invoke:
             * 
             * 1.) PreInit (Page)
             * 2.) Init (Control)
             * 3.) InitComplete (Page)
             * 4.) PreLoad (Page)
             * 5.) Load (Control)
             * 6.) PostBack Events
             * 7.) 
             * 
             * 
             * 
             * 
             */

            // protected internal virtual NameValueCollection DeterminePostBackMode(); -- Don't call this. It doesn't seem necessary.

            // 1.) MaintainScrollPositionOnPostBack -- Call this.
            // 2.) private void PerformPreInit(); -- Call this.
            // 3.) internal virtual void InitRecursive(Control namingContainer); -- Call this. -- InitRecursive(null);
            // 4.) protected virtual void OnInitComplete(EventArgs e); -- Call this. -- this.OnInitComplete(EventArgs.Empty);
            // private void LoadAllState(); -- Call this. -- LoadAllState(); <-- Probably DON'T call this because any ViewState will be loaded manually by the StateStrategy.
            // 5.) private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad); -- Call this. -- this.ProcessPostData(this._requestValueCollection, true);
            // 6.) protected virtual void OnPreLoad(EventArgs e); -- Call this. -- this.OnPreLoad(EventArgs.Empty);
            // 7.) internal virtual void LoadRecursive(); -- Call this. -- this.LoadRecursive();
            // 8.) private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad); -- Call this. -- this.ProcessPostData(this._leftoverPostData, false); -- This is using the this._leftoverPostData field that is a byproduct of the first call of the ProcessPostData method.
            // 9.) internal void RaiseChangedEvents(); -- Call this. -- this.RaiseChangedEvents();
            // 10.) private void RaisePostBackEvent(NameValueCollection postData); -- Call this. -- this.RaisePostBackEvent(this._requestValueCollection);
            // 11.) protected virtual void OnLoadComplete(EventArgs e); -- Call this. -- this.OnLoadComplete(EventArgs.Empty);
            // 12.) internal virtual void PreRenderRecursiveInternal(); -- Call this. -- PreRenderRecursiveInternal();
            // 13.) private void PerformPreRenderComplete(); -- Call this. -- this.PerformPreRenderComplete();
            // 14.) private void BuildPageProfileTree(bool enableViewState); -- Call this. -- this.BuildPageProfileTree(this.EnableViewState);
            // private void SaveAllState(); -- Don't call this. -- SaveAllState(); <-- The StateStrategy will handle this for us.
            // 15.) protected virtual void OnSaveStateComplete(EventArgs e); -- Call this. -- this.OnSaveStateComplete(EventArgs.Empty);
            // public virtual void RenderControl(HtmlTextWriter writer); -- Don't call this. -- this.RenderControl(this.CreateHtmlTextWriter(this.Response.Output));
            // 16.) Unload???

            
            CallbackStrategyArguments callbackArguments = (CallbackStrategyArguments)new DotNetJsonCallbackSerializer().DeserializeObject<CallbackStrategyArguments>(ControlParam);
            CallbackArguments = callbackArguments;

            if (callbackArguments.CallbackMethod == "LoadScript")
            {

            }


            bool isOutOfBand = callbackArguments.IsOutOfBand;
            Control outOfBandControl = null;
            IAjaxControl ajaxControl = null;


            Page page = CreatePageInstance(context, callbackArguments);


            NameValueCollection postData = (NameValueCollection)TypeOfPage.GetMethod("DeterminePostBackMode", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null).Invoke(page, null);
            TypeOfPage.GetField("_requestValueCollection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(page, postData);

            //  -- PerformPreInit -- 
            // The "PerformPreInit" method needs to be called against the page, where ASP.NET Themes are invoked and the MasterPage is applied. The method can't be skipped if we 
            // want to ensure that all controls will work exactly as they would in a non-AJAX, postback context.
            TypeOfPage.GetMethod("PerformPreInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, Type.EmptyTypes, null).Invoke(page, null);


            if (isOutOfBand && !callbackArguments.IsPage)
            {
                // Init the Header first.
                HtmlHead header = AjaxControlUtils.FindChildControlByType<HtmlHead>(page);

                if (header != null)
                {
                    typeof(Control).GetMethod("OnInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(EventArgs) }, null).Invoke(header, new object[] { EventArgs.Empty });
                }


                // Init the Form second.
                HtmlForm form = AjaxControlUtils.FindChildControlByType<HtmlForm>(page);

                if (form != null)
                {
                    typeof(Control).GetMethod("OnInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(EventArgs) }, null).Invoke(form, new object[] { EventArgs.Empty });
                }


                // Now Init the Page itself.
                typeof(Control).GetMethod("OnInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(EventArgs) }, null).Invoke(page, new object[] { EventArgs.Empty });


                // Ensure the ScriptManager is initialized correctly if one is added to the page.

                if (CurrentScriptManager != null)
                {
                    Type pageWrapperType = Type.GetType(Properties.Settings.Default.PageWrapper_TypeName, false);
                    object pageWrapper = pageWrapperType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(Page) }, null).Invoke(new object[] { page });

                    typeof(System.Web.UI.ScriptManager).GetField("_page", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(CurrentScriptManager, pageWrapper);
                }
            }
            else if (!callbackArguments.IsPage)
            {
                CallbackStrategy.InitRecursive(page, null);
            }


            if (isOutOfBand)
            {
                if (!callbackArguments.IsTemplateControl)
                {
                    outOfBandControl = AjaxControlUtils.FindChildControlByUniqueID(page, ControlID);

                    if (outOfBandControl != null)
                    {
                        ajaxControl = outOfBandControl as IAjaxControl;

                        if (ajaxControl != null)
                        {
                            ajaxControl.CallbackStrategy.UpdateControlProperties(callbackArguments.Properties);
                        }


                        // If the "outOfBandControl" is found (which is most likely since the entire Control hierarchy will have been loaded during the invocation of the "CreatePageInstance" 
                        // method), then its "Init" event will need to be fired.
                        typeof(Control).GetMethod("OnInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(EventArgs) }, null).Invoke(outOfBandControl, new object[] { EventArgs.Empty });
                    }

                    // *Note* - It's OK for now if the "outOfBandControl" was not found. The control in question might be added to the Control hierarchy dynamically during the 
                    // Load of the Page. 
                }
                else
                {
                    outOfBandControl = CreateOutOfBandControlInstance(page, callbackArguments);
                    ajaxControl = outOfBandControl as IAjaxControl;

                    InitOutOfBandControl(page, outOfBandControl, callbackArguments);
                }
            }
            else
            {
                if (callbackArguments.IsPage)
                {
                    ajaxControl = (IAjaxControl)page;
                }
                else
                {
                    ajaxControl = AjaxControlUtils.FindChildControlByUniqueID(page, ControlID) as IAjaxControl;
                }


                if (ajaxControl != null)
                {
                    ajaxControl.CallbackStrategy.LoadControl(callbackArguments);
                    ajaxControl.CallbackStrategy.UpdateControlProperties(callbackArguments.Properties);
                }

                // If ajaxControl could not be found, we'll wait and continue with the Page event lifecycle until the Load event and try again then to find the control in question.
            }


            //  -- OnInitComplete --
            // The "OnInitComplete" method should always be invoked, even for out-of-band callbacks, 
            TypeOfPage.GetMethod("OnInitComplete", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(EventArgs) }, null).Invoke(page, new object[] { EventArgs.Empty });
            
            
            //  -- ProcessPostData -- 
            // The "ProcessPostData" method should always be invoked, even for out-of-band callbacks. ** Add more comments **
            if (!callbackArguments.SkipPostBackData && callbackArguments.CallbackMethod != "Load")
            {
                TypeOfPage.GetMethod("ProcessPostData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(NameValueCollection), typeof(bool) }, null).Invoke(page, new object[] { postData, true });
            }


            if (isOutOfBand)
            {
                LoadOutOfBandControl(page, outOfBandControl, callbackArguments);
            }



            //  -- OnPreLoad -- 
            // ** Add more comments **
            TypeOfPage.GetMethod("OnPreLoad", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, new Type[] { typeof(EventArgs) }, null).Invoke(page, new object[] { EventArgs.Empty });
            
            

            //  -- Control.LoadRecursive and Page.OnLoad -- 
            // ** Add more comments **
            if (isOutOfBand)
            {
                // TODO: Load Control Adapters. Look at Control.LoadRecursive method in reflector for guidance.
                TypeOfPage.GetMethod("OnLoad", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(EventArgs) }, null).Invoke(page, new object[] { EventArgs.Empty });
            }
            else
            {
                CallbackStrategy.LoadRecursive(page);

                if (ajaxControl == null && !isOutOfBand)
                {
                    ajaxControl = AjaxControlUtils.FindChildControlByUniqueID(page, ControlID) as IAjaxControl;
                    if (ajaxControl == null) { throw new HttpException("An IAjaxControl instance could not be found in the Control hierarchy with a UniqueID of " + ControlID + "."); }

                    ajaxControl.CallbackStrategy.LoadControl(callbackArguments);
                    ajaxControl.CallbackStrategy.UpdateControlProperties(callbackArguments.Properties);
                }
            }


            if (!callbackArguments.SkipPostBackData && callbackArguments.CallbackMethod != "Load")
            {
                NameValueCollection leftOverPostData = (NameValueCollection)TypeOfPage.GetField("_leftoverPostData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(page);
                TypeOfPage.GetMethod("ProcessPostData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(NameValueCollection), typeof(bool) }, null).Invoke(page, new object[] { leftOverPostData, false });
            }
            
            TypeOfPage.GetMethod("RaiseChangedEvents", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null).Invoke(page, null);
            TypeOfPage.GetMethod("RaisePostBackEvent", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(NameValueCollection) }, null).Invoke(page, new object[] { postData });
            TypeOfPage.GetMethod("OnLoadComplete", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, new Type[] { typeof(EventArgs) }, null).Invoke(page, new object[] { EventArgs.Empty });


            // Generate the AjaxControlContext and process the callback.
            AjaxControlContext controlContext = ajaxControl.CallbackStrategy.GenerateControlContext(callbackArguments);
            ControlContext = controlContext;

            ajaxControl.CallbackStrategy.ProcessCallback(controlContext);

            
            // We're not rendering the page. // typeof(Page).GetMethod("PreRenderRecursiveInternal", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, Type.EmptyTypes, null).Invoke(page, null);
            // We're not rendering the page. // typeof(Page).GetMethod("PerformPreRenderComplete", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null).Invoke(page, null);
            // No Profile Tree building since trace information will never be sent back. // typeof(Page).GetMethod("BuildPageProfileTree", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(bool) }, null).Invoke(page, new object[] { page.EnableViewState });
            // This isn't necessary because the State Strategy handles all saving of state. // typeof(Page).GetMethod("OnSaveStateComplete", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, new Type[] { typeof(EventArgs) }, null).Invoke(page, new object[] { EventArgs.Empty });

            
            if (callbackArguments.GZipResponse)
            {
                byte[] resultBytes = GZipCallbackResults(ajaxControl.CallbackStrategy.GenerateCallbackResult(controlContext));
                context.Response.OutputStream.Write(resultBytes, 0, resultBytes.Length);

                context.Response.AppendHeader("Content-Encoding", "gzip");
            }
            else
            {
                context.Response.Write(ajaxControl.CallbackStrategy.GenerateCallbackResult(controlContext));
            }


            Current = null;
        }


        protected virtual Page CreatePageInstance(HttpContext context, CallbackStrategyArguments callbackArguments)
        {
            // URLRefferer contains the Absolute Path of the Default.aspx page. We'll need to get the VirtualPath from that. 
            //string pageVirtualPath = VirtualPathUtility.ToAbsolute("~/" + context.Server.MapPath(context.Request.UrlReferrer.AbsolutePath).Replace(context.Request.PhysicalApplicationPath, "").Replace("\\", "/"));

            // The original page will need to emit, via JavaScript, the virtual path of the handler.

            //string pageVirtualPath = "~/Default.aspx";

            //System.Web.Util.IWebObjectFactory objectFactory = BuildManager.GetObjectFactory(pageVirtualPath, false);
            System.Web.Util.IWebObjectFactory objectFactory = BuildManager.GetObjectFactory(callbackArguments.Path, false);
            Page page = (Page)objectFactory.CreateInstance();

            context.Handler = page;
            CurrentPage = page;


            if (callbackArguments.IsOutOfBand)
            {
                CurrentScriptManager = new System.Web.UI.ScriptManager();
            }


            typeof(Page).GetMethod("SetIntrinsics", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new Type[] { typeof(HttpContext) }, null).Invoke(page, new object[] { context });


            //  -- Invoke the Page's "FrameworkInitialize" method. -- 
            // The "FrameworkInitialize" method is what triggers the contruction of the Page's Control hierarchy. Found within an override of this method, that is implemented within  
            // the CodeGen file generated during compilation (located within \Temporary ASP.NET Files\ for dynamically compiled apps, while found within resultant DLL for pre-compiled 
            // apps), the "FrameworkInitialize" method calls a dynamically generated method called "@__BuildControlTree" that will iterate through all literal and server-side control 
            // content output by the native ControlBuilders, and build up the Control hierarchy. This process is recursive in a way. Controls added to the hierarchy that contain 
            // their own child controls (these would be the child controls that derive from TemplateControl, which if it is the Page's "FrameworkInitialize" method currently being 
            // invoked, will ONLY be UserControls since UserControls are the only other derivative of TemplateControl supported natively by ASP.NET) will be instantiated and before 
            // being added to the Controls collection, will have their "InitializeAsUserControl" method invoked which will in turn invoke its "FrameworkInitialize" method.
            //
            // Since the entire Page lifecycle has been usurped by the AjaxControlHandler, we have the opportunity to further optimize the AJAX Control Framework's callback lifecycle. 
            // 
            //if (!(callbackArguments.IsOutOfBand && callbackArguments.IsTemplateControl))
            //{
                TypeOfPage.GetMethod("FrameworkInitialize", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, Type.EmptyTypes, null).Invoke(page, null);
            //}


            typeof(Control).GetField("_controlState", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(page, 1);
            TypeOfPage.GetField("_isCallback", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(page, true);

            return page;
        }


        protected virtual Control CreateOutOfBandControlInstance(Page page, CallbackStrategyArguments callbackArguments)
        {
            if (String.IsNullOrEmpty(callbackArguments.ControlType))
            {
                throw new InvalidOperationException("The \"ControlType\" callback argument is required to invoke Global callback methods.");
            }


            Control outOfBandControl = null;

            if (callbackArguments.IsPage)
            {
                outOfBandControl = page;
            }
            else
            {
                outOfBandControl = CreateControl(callbackArguments.ResolvedControlType);
            }


            if (outOfBandControl == null)
            {
                throw new ArgumentException("The control Type string provided does not resolve to a valid object type. Be sure to provide the fully qualified type name of the global control being invoked/updated.");
            }

            
            TemplateControl templateControl = outOfBandControl as TemplateControl;
            if (templateControl != null)
            {
                if (templateControl.AppRelativeVirtualPath == null || templateControl.AppRelativeVirtualPath.Trim().Length == 0)
                {
                    throw new InvalidOperationException("IAjaxControl implementations that derive from " + typeof(TemplateControl).FullName + " must provide a valid AppRelativeVirtualPath property value.");
                }


                Type compiledTypeThatIncludesMarkup = System.Web.Compilation.BuildManager.GetCompiledType(templateControl.AppRelativeVirtualPath);
                if (compiledTypeThatIncludesMarkup != null && compiledTypeThatIncludesMarkup.FullName != callbackArguments.ResolvedControlType.FullName)
                {
                    outOfBandControl = CreateControl(compiledTypeThatIncludesMarkup);
                }
            }
            


            if (outOfBandControl is IAjaxControl)
            {
                CallbackStrategy callbackStrategy = (outOfBandControl as IAjaxControl).CallbackStrategy;

                // IAjaxControl instances being loaded globally should use their own loading logic.
                callbackStrategy.TargetControlType = callbackArguments.ResolvedControlType;
                callbackStrategy.ControlInvokedCallback = ((string)callbackArguments.Properties["UniqueID"] == ControlID);

                callbackStrategy.LoadControl(callbackArguments);

                if (callbackArguments.CallbackMethod != "Load")
                {
                    callbackStrategy.UpdateControlProperties(callbackArguments.Properties);
                }
                else if (callbackArguments.LoadProperties != null)
                {
                    callbackStrategy.UpdateControlProperties(callbackArguments.LoadProperties);
                }

                outOfBandControl = callbackStrategy.TargetControl;
            }


            outOfBandControl.Page = page;


            return outOfBandControl;
        }


        protected virtual void InitOutOfBandControl(Page page, Control outOfBandControl, CallbackStrategyArguments callbackArguments)
        {
            if (callbackArguments.IsPage)
            {
                CallbackStrategy.InitRecursive(page, null);
            }
            else
            {
                CallbackStrategy.InitRecursive(outOfBandControl, page);
            }


            object tempClientIDMode;
            if (callbackArguments.Properties.TryGetValue("ClientIDMode", out tempClientIDMode))
            {
                outOfBandControl.ClientIDMode = (ClientIDMode)((int)tempClientIDMode);
            }
            else
            {
                outOfBandControl.ClientIDMode = ClientIDMode.AutoID;
            }


            if (!callbackArguments.IsPage)
            {
                string outOfBandControlClientID = null;
                string uniqueID = (string)callbackArguments.Properties["UniqueID"];
                char idSeparator = AjaxControlUtils.GetIdSeparator(outOfBandControl);

                if (callbackArguments.Properties.ContainsKey("HasDataContainer") && (bool)callbackArguments.Properties["HasDataContainer"] == true)
                {
                    DataItemContainerPlaceHolder dataItemContainer = new DataItemContainerPlaceHolder();
                    dataItemContainer.ClientIDMode = outOfBandControl.ClientIDMode;

                    page.Form.Controls.Add(dataItemContainer);
                    dataItemContainer.Controls.Add(outOfBandControl);

                    CallbackStrategy.SetParent(dataItemContainer, null);

                    if (callbackArguments.CallbackMethod == "Load")
                    {
                        outOfBandControlClientID = uniqueID + idSeparator + callbackArguments.OutOfBandID;
                    }
                    else
                    {
                        string id = (string)callbackArguments.Properties["ID"];

                        if (id == null)
                        {
                            outOfBandControlClientID = uniqueID;
                        }
                        else
                        {
                            outOfBandControlClientID = uniqueID.Substring(0, uniqueID.LastIndexOf(id)) + id;
                        }
                    }
                }
                else
                {
                    // Suppress the ReadOnly error on the <form>'s Controls collection. We can get away with doing this 
                    // without worrying about any negative effect because real issues are only relevant when the control 
                    // is being rendered, and we're suppressing the ReadOnly error on the <form>'s Controls collection, 
                    // which is outside of the context of the AJAX Control being operated against.
                    string previousErrorMsg = RenderingHelper.GetReadOnlyErrorMsg(page.Form.Controls);
                    if (previousErrorMsg != null)
                    {
                        RenderingHelper.SetReadOnlyErrorMsg(page.Form.Controls, null);
                    }


                    page.Form.Controls.Add(outOfBandControl);

                    if (callbackArguments.CallbackMethod == "Load")
                    {
                        outOfBandControlClientID = uniqueID + idSeparator + callbackArguments.OutOfBandID;
                    }
                    else
                    {
                        outOfBandControlClientID = uniqueID;
                    }
                }


                if (outOfBandControl.NamingContainer != null && outOfBandControl.NamingContainer != outOfBandControl.Page && (outOfBandControl.ClientIDMode == ClientIDMode.AutoID || outOfBandControl.ClientIDMode == ClientIDMode.Predictable))
                {
                    outOfBandControlClientID = outOfBandControlClientID.Substring(outOfBandControl.NamingContainer.UniqueID.Length + 1);
                }

                outOfBandControl.ID = outOfBandControlClientID;
            }


            if (outOfBandControl is IAjaxControl)
            {
                // Load the ControlState and ViewState of the global Control.
                // TODO: Make sure taht this conditional doesn't pass if any Parent controls have EnableViewState set to false 
                if ((outOfBandControl as IAjaxControl).StateStrategy != null && callbackArguments.CallbackMethod != "Load" && outOfBandControl.EnableViewState)
                {
                    // TODO: State loading twice????!! It probably shouldn't be happening here. If what's going on here is correct, document in detail why.
                    (outOfBandControl as IAjaxControl).StateStrategy.Load();
                }
            }
        }


        protected virtual void LoadOutOfBandControl(Page page, Control outOfBandControl, CallbackStrategyArguments callbackArguments)
        {
            // Force the post data to be processed against the out-of-band control.
            if (callbackArguments.CallbackMethod != "Load" && !callbackArguments.SkipPostBackData)
            {
                if (outOfBandControl is IPostBackDataHandler)
                {
                    (outOfBandControl as IPostBackDataHandler).LoadPostData(outOfBandControl.UniqueID, page.Request.Form);
                }


                List<IPostBackDataHandler> postBackDataHandlers = AjaxControlUtils.FindChildControlsByType<IPostBackDataHandler>(outOfBandControl);

                foreach (IPostBackDataHandler postBackDataHandler in postBackDataHandlers)
                {
                    try
                    {
                        postBackDataHandler.LoadPostData((postBackDataHandler as Control).UniqueID, page.Request.Form);
                    }
                    catch
                    {
                        /* Do nothing */
                    }
                }
            }


            CallbackStrategy.LoadRecursive(outOfBandControl);
        }


        protected virtual byte[] GZipCallbackResults(string callbackResult)
        {
            byte[] resultBytes = null;
            MemoryStream memoryStream = new MemoryStream();

            using (GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
            {
                resultBytes = Encoding.UTF8.GetBytes(callbackResult);
                gzipStream.Write(resultBytes, 0, resultBytes.Length);
            }

            resultBytes = memoryStream.ToArray();
            memoryStream.Close();

            return resultBytes;
        }


        protected virtual Control CreateControl(string fullyQualifiedTypeName)
        {
            Type controlType = AjaxControlUtils.ResolveControlType(fullyQualifiedTypeName);
            if (controlType == null) { return null; }

            object control = null;

            if (TypeCache.CreateObject(controlType, out control))
            {
                return control as Control;
            }

            return null;
        }


        protected virtual Control CreateControl(Type controlType)
        {
            object control = null;

            if (TypeCache.CreateObject(controlType, out control))
            {
                return control as Control;
            }

            return null;
        }


        public static void Abort()
        {
            if (Current != null && IsCallback)
            {
                Current.Response.ClearContent();
                Current.Response.Write("a"); // 'a' for "abort". Clever, ay?
                Current.Response.End();
            }
        }
        //------\\ Methods //-----------------------------------------------//
    }
}